﻿namespace Stopwatch
{
    using System;
    using System.Runtime.Serialization;

    [DataContract]
    public struct Time : IComparable<Time>
    {
        public static readonly Time Zero = new Time();

        public Time(int hours = 0, int minutes = 0, int seconds = 0, int millisecond = 0)
            : this()
        {
            Hours = hours;
            Minutes = 0;
            Seconds = 0;
            Milliseconds = 0;

            if (minutes > 0 && minutes < 60)
            {
                Minutes = minutes;
            }

            if (seconds > 0 && seconds < 60)
            {
                Seconds = seconds;
            }

            if (millisecond > 0 && millisecond < 1000)
            {
                Milliseconds = millisecond;
            }
        }

        [DataMember]
        public int Hours { get; set; }

        [DataMember]
        public int Minutes { get; set; }

        [DataMember]
        public int Seconds { get; set; }

        [DataMember]
        public int Milliseconds { get; set; }

        #region overrides

        public override string ToString()
        {
            return string.Format("{0:D2}:{1:D2}:{2:D2}.{3:D2}", Hours, Minutes, Seconds, Milliseconds);
        }
        public override bool Equals(object obj)
        {
            Time other = (Time)obj;
            if (other == null)
            {
                return false;
            }

            return this == other || this.CompareTo(other) == 0;
        }

        public override int GetHashCode()
        {
            return ConverToAnd(this).GetHashCode();
        }

        #endregion

        #region IComparable<Time>

        public int CompareTo(Time other)
        {
            return (int)(ConverToAnd(this) - ConverToAnd(other));
        }

        #endregion

        #region operators

        public static bool operator <(Time t1, Time t2)
        {
            return t1.CompareTo(t2) < 0;
        }

        public static bool operator <=(Time t1, Time t2)
        {
            return t1.CompareTo(t2) <= 0;
        }

        public static bool operator >(Time t1, Time t2)
        {
            return t1.CompareTo(t2) > 0;
        }

        public static bool operator >=(Time t1, Time t2)
        {
            return t1.CompareTo(t2) >= 0;
        }

        public static bool operator ==(Time t1, Time t2)
        {
            return t1.CompareTo(t2) == 0;
        }

        public static bool operator !=(Time t1, Time t2)
        {
            return t1.CompareTo(t2) != 0;
        }

        public static Time operator +(Time t1, Time t2)
        {
            t1 = t1.AddMilliseconds(t2.Milliseconds);
            t1 = t1.AddSeconds(t2.Seconds);
            t1 = t1.AddMinutes(t2.Minutes);
            t1 = t1.AddHours(t2.Hours);

            return t1;
        }

        public static Time operator -(Time t1, Time t2)
        {
            t1 = t1.AddMilliseconds(-t2.Milliseconds);
            t1 = t1.AddSeconds(-t2.Seconds);
            t1 = t1.AddMinutes(-t2.Minutes);
            t1 = t1.AddHours(-t2.Hours);

            return t1;
        }

        Time AddMilliseconds(int millisecond)
        {
            Milliseconds += millisecond;

            int offset = Milliseconds / 1000;
            Milliseconds %= 1000;

            if (Milliseconds < 0)
            {
                offset -= 1;
                Milliseconds += 1000;
            }

            if (offset != 0)
            {
                AddSeconds(offset);
            }

            return this;
        }

        Time AddSeconds(int seconds)
        {
            Seconds += seconds;

            int offset = Seconds / 60;
            Seconds %= 60;

            if (Seconds < 0)
            {
                offset -= 1;
                Seconds += 60;
            }

            if (offset != 0)
            {
                AddMinutes(offset);
            }

            return this;
        }

        Time AddMinutes(int minutes)
        {
            Minutes += minutes;

            int offset = Minutes / 60;
            Minutes %= 60;

            if (Minutes < 0)
            {
                offset -= 1;
                Minutes += 60;
            }

            if (offset != 0)
            {
                AddHours(offset);
            }

            return this;
        }

        Time AddHours(int hours)
        {
            Hours += hours;
            return this;
        }

        #endregion

        static long ConverToAnd(Time time)
        {
            return time.Hours * 1000 * 100 * 100 + time.Minutes * 1000 * 100 + time.Seconds * 1000 + time.Milliseconds;
        }
    }
}
